﻿#include "Player.h"

#include "App/Game.h"
#include "World/WorldRayCast.h"
#include "Physics/Movement.h"

Player::Player(const Ref<World> &world, const Ref<Persistence> &persistence)
    : m_world(world),
      m_persistence(persistence),
      m_camera(m_persistence->GetCamera())
{
  Window::GetInstance().GetDispatcher().Subscribe(
      EventType::WindowResize,
      [this](const Event &e)
      {
        const auto &event = static_cast<const WindowResizeEvent &>(e);
        if (event.GetWidth() > 0 && event.GetHeight() > 0)
          m_camera->SetAspectRatio(event.GetWidth() / (float)event.GetHeight());
      });
}

Player::~Player()
{
  m_persistence->CommitCamera(m_camera);
}

glm::vec3 Player::GetForward() const
{
  const float yaw = GetRotation().y;
  return {sin(yaw), 0.f, cos(yaw)};
}

glm::vec3 Player::GetUp() const
{
  return {0, 1, 0};
}

glm::vec3 Player::GetRight() const
{
  const float yaw = GetRotation().y;
  return {cos(yaw), 0.f, -sin(yaw)};
}

void Player::SetPosition(const glm::vec3 &position)
{
  if (m_camera)
  {
    m_camera->SetPosition(position);
  }
}

void Player::SetRotation(const glm::vec3 &rotation)
{
  if (m_camera)
  {
    m_camera->SetRotation(rotation);
  }
}

glm::vec3 Player::GetPosition() const
{
  return m_camera->GetPosition(); // - glm::vec3(0, cameraHeight, 0);
}

glm::vec3 Player::GetRotation() const
{
  return m_camera->GetRotation();
}

void Player::EnableMouseCursor(bool enable)
{
  Window::GetInstance().SetEnableCursor(enable);
  m_isUsingMouse = !enable;
}

void Player::ToggleMouseCursor()
{
  EnableMouseCursor(m_isUsingMouse);
}

void Player::Update(float deltaTime)
{
  DoCrosshairRayCast();
  DoMove(deltaTime);
  DoJump(deltaTime);
  DoRotateByMouse(deltaTime);
  // DoRotateByKey(deltaTime);
  DoBlockInteraction(deltaTime);
  DoEscapeCheck();
  DoDirectionCheck();
}

void Player::TryJump(bool force)
{
  if (m_canJump || force)
  {
    m_gravity = glm::vec3(0, GetJumpSpeed(), 0);
    m_canJump = false;
  }
}

float Player::GetSpeed() const
{
  return (m_isRunning ? runningSpeedMultiplier : walkingSpeedMultiplier) * 3.8f;
}

float Player::GetJumpSpeed() const
{
  return jumpSpeedMultiplier * gravityConstant * 0.28f;
}

void Player::DoCrosshairRayCast()
{
  const auto &level = Game::GetInstance().GetLevel();

  WorldRayCast ray{m_camera->GetPosition(), m_camera->GetLookAt(), reach, *level->GetWorld()};
  if (ray.HasHit())
  {
    m_hitTarget = ray.GetHitTarget();
    m_hasHit = true;
  }
  else
  {
    m_hitTarget = {};
    m_hasHit = false;
  }
}

void Player::DoMove(float deltaTime)
{
  auto &game = Game::GetInstance();
  auto world = game.GetLevel()->GetWorld();
  auto input = game.GetInput();

  if (!input)
    return;

  const glm::vec3 forward = GetForward();
  const glm::vec3 right = GetRight();
  const glm::vec3 up = GetUp();

  static float xSign = 0;
  static float ySign = 0;
  static float zSign = 0;

  if (input->IsKeyJustPressed(GLFW_KEY_W))
    xSign = -1;
  if (input->IsKeyJustPressed(GLFW_KEY_S))
    xSign = 1;
  else if (input->IsKeyJustReleased(GLFW_KEY_W) ||
           input->IsKeyJustReleased(GLFW_KEY_S))
    xSign = 0;

  if (input->IsKeyPressing(GLFW_KEY_A))
    zSign = -1;
  if (input->IsKeyPressing(GLFW_KEY_D))
    zSign = 1;
  else if (input->IsKeyJustReleased(GLFW_KEY_A) ||
           input->IsKeyJustReleased(GLFW_KEY_D))
    zSign = 0;

  if (game.CheckGameMode(Game::Mode::Creative))
  {
    if (input->IsKeyPressing(GLFW_KEY_LEFT_SHIFT))
      ySign = -1;
    if (input->IsKeyPressing(GLFW_KEY_SPACE))
      ySign = 1;
    else if (input->IsKeyJustReleased(GLFW_KEY_LEFT_SHIFT) ||
             input->IsKeyJustReleased(GLFW_KEY_SPACE))
      ySign = 0;
  }

  SetRunning(input->IsKeyPressing(GLFW_KEY_LEFT_CONTROL));

  glm::vec3 position = GetPosition();

  const float MaxSpeed = GetSpeed(); // 获取当前最大速度
  // 计算输入方向
  glm::vec3 inputDir = xSign * forward + zSign * right;
  if (glm::length(inputDir) > 0)
  {
    inputDir = glm::normalize(inputDir);
  }
  // 应用加速度
  glm::vec3 targetVelocity = inputDir * MaxSpeed;
  m_velocity = glm::mix(m_velocity, targetVelocity, m_acceleration * (!m_canJump ? inSkyHorizontalSpeedMultiplier : 1.0f) * deltaTime);
  // 应用摩擦（当没有输入时减速）
  if (xSign == 0 && zSign == 0)
  {
    m_velocity *= (1.0f - m_friction * deltaTime);
    if (glm::length(m_velocity) < 0.1f)
    {
      m_velocity = glm::vec3(0);
    }
  }

  if (game.CheckGameMode(Game::Mode::Survival))
  {
    glm::vec3 movement = m_velocity * deltaTime;

    static constexpr std::array<glm::vec3, 3> axes = {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}};

    for (int i = 0; i < 3; i++)
    {
      const glm::vec3 axis = axes[i];

      glm::vec3 movementInAxis = movement * axis;

      bool feetCheck = Movement::CanMove(position, position + movementInAxis, *world);
      bool headCheck = Movement::CanMove(position + glm::vec3(0, 1, 0), position + movementInAxis + glm::vec3(0, 1, 0), *world);

      if (feetCheck)
      {
        position += movementInAxis;
      }

      else if (!feetCheck && headCheck && i != 1)
      {
        if (m_canJump && autoJump)
        {
          TryJump();
        }
      }
    }

    glm::vec3 positionWithGravity = position + m_gravity * deltaTime;
    if (Movement::CanMove(position, positionWithGravity, *world))
    {
      position = positionWithGravity;
    }
    else
    {
      m_canJump = true;
      m_gravity = glm::vec3(0);
      if (!Movement::CanMove(position + glm::vec3(0, 1, 0), position, *world))
      {
        // 可能卡在方块，尝试在y轴上上下移动一格到空位，需要预留两格高的空间
        if (Movement::CanMove(position + glm::vec3(0, 2, 0), positionWithGravity + glm::vec3(0, 2, 0), *world))
        {
          position += glm::vec3(0, 10.f * deltaTime, 0);
        }
        else if (Movement::CanMove(position + glm::vec3(0, -2, 0), positionWithGravity + glm::vec3(0, -2, 0), *world))
        {
          position += glm::vec3(0, -10.f * deltaTime, 0);
        }
      }
    }
  }
  else
  {
    const float movingDelta = GetSpeed() * deltaTime;
    glm::vec3 movement = xSign * forward * movingDelta +
                         ySign * up * movingDelta +
                         zSign * right * movingDelta;
    position += movement;
  }

  SetPosition(position);

  if (game.CheckGameMode(Game::Mode::Survival))
    m_gravity += glm::vec3(0, -1, 0) * gravityConstant * deltaTime;
  else
    m_gravity = glm::vec3(0.f);
}

void Player::DoJump(float deltaTime)
{
  auto input = Game::GetInstance().GetInput();
  if (input->IsKeyPressing(GLFW_KEY_SPACE))
  {
    TryJump();
  }
}

void Player::DoRotateByKey(float deltaTime)
{
  auto input = Game::GetInstance().GetInput();
  if (!input)
    return;

  if (!m_isUsingMouse)
  {
    float xSign = 0;
    float ySign = 0;

    const float rotatingDelta = rotatingSpeed * deltaTime;

    if (input->IsKeyPressing(GLFW_KEY_LEFT))
      ySign = 1;
    if (input->IsKeyPressing(GLFW_KEY_RIGHT))
      ySign = -1;
    if (input->IsKeyPressing(GLFW_KEY_UP))
      xSign = 1;
    if (input->IsKeyPressing(GLFW_KEY_DOWN))
      xSign = -1;

    if (xSign || ySign)
    {
      glm::vec3 rotate = glm::degrees(m_camera->GetRotation());
      rotate.x += xSign * rotatingDelta;
      rotate.y += ySign * rotatingDelta;
      // 限制旋转角度
      rotate.x = std::clamp(rotate.x, -89.0f, 89.0f);
      // 对于y，要限制在0-360之间，如果超出了，要加上360或者减去360
      if (rotate.y < 0)
        rotate.y += 360;
      else if (rotate.y > 360)
        rotate.y -= 360;
      m_camera->SetRotation(glm::radians(rotate));
    }
  }
}

void Player::DoRotateByMouse(float deltaTime)
{
  auto input = Game::GetInstance().GetInput();
  if (!input)
    return;

  const glm::vec2 mousePos = input->GetMousePosition();
  static float lastX = mousePos.x;
  static float lastY = mousePos.y;

  if (m_isUsingMouse)
  {
    const float rotatingDelta = mouseSensitivity;
    glm::vec3 rotate = glm::degrees(m_camera->GetRotation());
    rotate.y += (lastX - mousePos.x) * rotatingDelta;
    rotate.x += (lastY - mousePos.y) * rotatingDelta;
    // 限制旋转角度
    rotate.x = std::clamp(rotate.x, -89.0f, 89.0f);
    // 对于y，要限制在0-360之间，如果超出了，要加上360或者减去360
    if (rotate.y < 0)
      rotate.y += 360;
    else if (rotate.y > 360)
      rotate.y -= 360;
    m_camera->SetRotation(glm::radians(rotate));
  }

  lastX = mousePos.x;
  lastY = mousePos.y;
}

void Player::DoBlockInteraction(float deltaTime)
{
  auto &game = Game::GetInstance();
  auto input = game.GetInput();
  if (!input || !m_isUsingMouse)
    return;

  auto world = game.GetLevel()->GetWorld();
  assert(world != nullptr);

  if (input->IsMouseButtonJustPressed(GLFW_MOUSE_BUTTON_LEFT))
  {
    if (WorldRayCast ray{m_camera->GetPosition(), m_camera->GetLookAt(), reach, *world})
    {
      auto hitTarget = ray.GetHitTarget();
      world->PlaceBlock(hitTarget.blockPosition, BlockData{0}, this);
    }
  }
  else if (input->IsMouseButtonJustPressed(GLFW_MOUSE_BUTTON_RIGHT))
  {

    if (WorldRayCast ray{m_camera->GetPosition(), m_camera->GetLookAt(), reach, *world})
    {
      auto hitTarget = ray.GetHitTarget();
      if (hitTarget.hasNeighbor && blockToPalce.id != 0)
      {
        // bool selfNotInBlock = Movement::CanMove(GetPosition(), hitTarget.neighborPosition, *world);
        // if (selfNotInBlock)
        {
          world->PlaceBlock(

              hitTarget.neighborPosition,
              {blockToPalce.id,
               Direction::MatchDirctionByRotationY(GetRotation().y)
                   .GetOppsite()},
              this);
        }
      }
    }
  }
  else if (input->IsMouseButtonJustPressed(GLFW_MOUSE_BUTTON_MIDDLE))
  {
    if (WorldRayCast ray{m_camera->GetPosition(), m_camera->GetLookAt(), reach, *world})
    {
      blockToPalce = *ray.GetHitTarget().block;
    }
  }
}

void Player::DoEscapeCheck()
{
  auto input = Game::GetInstance().GetInput();
  if (input->IsKeyJustPressed(GLFW_KEY_ESCAPE))
  {
    EnableMouseCursor(m_isUsingMouse);
  }
}

void Player::DoDirectionCheck()
{
  m_direction = Direction::MatchDirctionByRotation(m_camera->GetRotation());
}
